/*
 *
 * Written by NIIBE Yutaka
 *
 * Copyright (C) 2009 Free Software Initiative of Japan
 *
 * Distributed under the terms of GPLv3 (or later)
 */

/*
 * You need libusb to use this program.
 *
 * On Debian, type:
 * $ apt-get libusb-dev
 */

/*
 * $ gcc -o akiusb-ctrl akiusb-ctrl.c -lusb
 * $ sudo ./akiusb-ctrl
 */

#include <stdio.h>
#include <string.h>
#include <usb.h>

#include <poll.h>

static int verbose = 0;

#define USB_VENDOR  0x16c0
#define USB_PRODUCT 0x05dc
#define USB_MANUFACTURER_STR "fsij.org"
#define USB_PRODUCT_STR "Aki-USB"
#define USB_SERIALNUMBER_STR "0"

#define ENDPOINT     2

#define POS_MAX 32
static int
compare_usb_string (usb_dev_handle *udev, int index, char *str)
{
  int len;
  char buf[256];

  if ((len = usb_get_string_simple (udev, index, buf, 256)) < 0)
    {
      perror ("USB_GET_STRING_SIMPLE failed\n");
      return -1;
    }
  
  return strncmp (buf, str, len);
}

#define USBLOWSPEED_BUFSIZE 8
char usbbuf[USBLOWSPEED_BUFSIZE];

#define AKIUSB_DRAW   0
#define AKIUSB_SETPTR 1
static int
send_cmd_setptr (usb_dev_handle *udev, unsigned char col)
{
  int r;

  usbbuf[0] = AKIUSB_SETPTR;
  usbbuf[1] = col;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

#define AKIUSB_CLRSCR 2
static int
send_cmd_clrscr (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_CLRSCR;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}
static int clear_columns (usb_dev_handle *udev, int n);

#define AKIUSB_CHAR 4
static int
type (usb_dev_handle *udev, char c)
{
  static int p = 0;
  int char_columns;

  int r;

  if(send_cmd_setptr (udev, p*2) < 0)
    return -1;

  /* clear a column before the character */
  clear_columns (udev, 1);

  usbbuf[0]= AKIUSB_CHAR;
  usbbuf[1]= c;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  p++;
  if (p >= POS_MAX)
    p = 0;

  return 0;
}

#define AKIUSB_VMODE 3
static int
send_cmd_vmode (usb_dev_handle *udev, unsigned char mode, unsigned char value)
{
  int r;

  usbbuf[0] = AKIUSB_VMODE;
  usbbuf[1] = mode;
  if (mode == 0)
    usbbuf[2] = value;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf,\
			       	(mode == 0) ? 3 : 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

static int draw (usb_dev_handle *udev, int pattern_no);
#define MAX_PATTERN 3

int
main (int argc, char *argv[])
{
  struct usb_bus *bus;
  struct usb_device *dev;
  usb_dev_handle *udev;
  int i;
  int n = 0;

  /* doesn't change anything at the moment */
  if (argc > 1 && !strcmp(argv[1], "-v"))
    verbose = 1;

  usb_init();

  usb_find_busses();
  usb_find_devices();

  /* iterating through every device on every bus */
  for (bus = usb_busses; bus; bus = bus->next)
    for (dev = bus->devices; dev; dev = dev->next)
      {
	printf ("%04x:%04x\n", dev->descriptor.idVendor,\
	       	dev->descriptor.idProduct);

	/* Check the vendor ID and product ID */
	if (dev->descriptor.idVendor == USB_VENDOR
	    && dev->descriptor.idProduct == USB_PRODUCT)
	  {
	    /* XXX: assert dev->descriptor.bNumConfigurations == 1 */

	    /* Check the interface */
	    for (i = 0; i < dev->config[0].bNumInterfaces; i++)
	      {
	  	/* XXX
		 * assert dev->config[0].interface[i].num_altsetting == 1
		 */
		
		/* Check the class */
	  	if (dev->config[0].interface[i].altsetting[0].bInterfaceClass\
		    == 0xff) /* Vendor specific */
		  {
		    /* Ok, it looks like our product */
		    /* Check the strings */

		    udev = usb_open (dev);
		    if (!udev)
		      {
			perror ("USB_OPEN failed\n");
			return 1;
		      }

		    if (compare_usb_string (udev, dev->descriptor.iManufacturer,\
					    USB_MANUFACTURER_STR) == 0 \
			&& compare_usb_string (udev, dev->descriptor.iProduct,\
					       USB_PRODUCT_STR) == 0 \
			&& compare_usb_string (udev,\
					       dev->descriptor.iSerialNumber,\
					       USB_SERIALNUMBER_STR) == 0)
		      goto found;

		    usb_close (udev);
		  }
	      }
	  }
      }

  puts ("No device found");
  return 1;


 found:

  printf ("interface %d\n", i);
  if (usb_set_configuration (udev, 1) < 0)
    {
      perror ("USB_SET_CONFIGURATION");
      /* Fall through */
    }

  if (usb_claim_interface (udev, i) < 0)
    {
      perror ("USB_CLAIM_INTERFACE");
      return 1;
    }

#define TTY_BUFSIZE 256
#define TIMEOUT 30

  if (send_cmd_vmode (udev, 0, 12) < 0)
    exit (1);

  if (send_cmd_clrscr (udev) < 0)
    exit (1);

  if (send_cmd_setptr (udev, 0) < 0)
    exit (1);

  if (draw (udev, n) < 0)
    exit (1);


  while (1)
    {
      struct pollfd pfd = { 0 /* standard input */, POLLIN, 0 };
      int r;
      char buf[TTY_BUFSIZE];

      if ((r = poll (&pfd, 1, TIMEOUT)) < 0)
	{
	  perror ("poll\n");
	  exit (1);
	}

      if (r)
	{
	  n++;
	  if (n >= MAX_PATTERN)
	    n = 0;

	  /* from what I understand, this is only to clear
	   * the stdin buffer */
	  if ((r = read (STDIN_FILENO, buf, TTY_BUFSIZE)) <= 0)
	    break;

	  if (send_cmd_clrscr (udev) < 0)
	    exit (1);
	}
/*
      if (draw (udev, n) < 0)
	exit (1);*/
      if (type (udev,'D') < 0 )
	exit (1);
    }

  usb_close (udev);

  return 0;
}

static int
clear_columns (usb_dev_handle *udev, int n)
{
  int i, j, r, l;

  l = n / 3;
  for (i = 0; i < l; i++)
    {
      usbbuf[0] = AKIUSB_DRAW;
      for (j = 0; j < 6; j++)
	usbbuf[j+1] = 0;

      if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
	{
	  perror ("USB_INTERRUPT_WRITE 1");
	  return r;
	}
    }

  if ((n % 3))
    {
      usbbuf[0] = AKIUSB_DRAW;
      for (j = 0; j < (n % 3)*2; j++)
	usbbuf[j+1] = 0;

      if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf,\
				    1 + (n % 3)*2, 0)) < 0)
	{
	  perror ("USB_INTERRUPT_WRITE 1");
	  return r;
	}
    }

  return 0;
}

static int draw_bug (usb_dev_handle *udev, int pattern_no);

static int
draw (usb_dev_handle *udev, int pattern_no)
{
  static int p = 0;
  int bug_columns;

  if (send_cmd_setptr (udev, p*2) < 0)
    return -1;

  /* Clear a column before the bug */
  clear_columns (udev, 1);

  /* Draw a bug */
  bug_columns = draw_bug (udev, pattern_no);
  if (bug_columns < 0)
    return -1;

  p++;
  if (p >= POS_MAX)
    p = 0;

  return 0;
}

static int
draw_bug_a_0 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0x70;
  usbbuf[3]= 0x00; usbbuf[4]= 0x18;
  usbbuf[5]= 0x00; usbbuf[6]= 0x7d;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0xb6;
  usbbuf[3]= 0x00; usbbuf[4]= 0xbc;
  usbbuf[5]= 0x00; usbbuf[6]= 0x3c;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0xbc;
  usbbuf[3]= 0x00; usbbuf[4]= 0xb6;
  usbbuf[5]= 0x00; usbbuf[6]= 0x7d;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0x18;
  usbbuf[3]= 0x00; usbbuf[4]= 0x70;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 5, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return 1;
    }

  return 11;
}

static int
draw_bug_a_1 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0x1e;
  usbbuf[3]= 0x00; usbbuf[4]= 0xb8;
  usbbuf[5]= 0x00; usbbuf[6]= 0x7d;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0x36;
  usbbuf[3]= 0x00; usbbuf[4]= 0x3c;
  usbbuf[5]= 0x00; usbbuf[6]= 0x3c;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0x3c;
  usbbuf[3]= 0x00; usbbuf[4]= 0x36;
  usbbuf[5]= 0x00; usbbuf[6]= 0x7d;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x00; usbbuf[2]= 0xb8;
  usbbuf[3]= 0x00; usbbuf[4]= 0x1e;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 5, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return 1;
    }

  return 11;
}

static int
draw_bug_b_0 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x98; usbbuf[2]= 0x00;
  usbbuf[3]= 0x5c; usbbuf[4]= 0x00;
  usbbuf[5]= 0xb6; usbbuf[6]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x5f; usbbuf[2]= 0x00;
  usbbuf[3]= 0x5f; usbbuf[4]= 0x00;
  usbbuf[5]= 0xb6; usbbuf[6]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x5c; usbbuf[2]= 0x00;
  usbbuf[3]= 0x98; usbbuf[4]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 5, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  return 8;
}

static int
draw_bug_b_1 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x58; usbbuf[2]= 0x00;
  usbbuf[3]= 0xbc; usbbuf[4]= 0x00;
  usbbuf[5]= 0x16; usbbuf[6]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x3f; usbbuf[2]= 0x00;
  usbbuf[3]= 0x3f; usbbuf[4]= 0x00;
  usbbuf[5]= 0x16; usbbuf[6]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0xbc; usbbuf[2]= 0x00;
  usbbuf[3]= 0x58; usbbuf[4]= 0x00;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 5, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  return 8;
}

static int
draw_bug_c_0 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x09; usbbuf[2]= 0xc0;
  usbbuf[3]= 0x09; usbbuf[4]= 0xe0;
  usbbuf[5]= 0x05; usbbuf[6]= 0xe0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x07; usbbuf[2]= 0x60;
  usbbuf[3]= 0x03; usbbuf[4]= 0x70;
  usbbuf[5]= 0x05; usbbuf[6]= 0xf0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x05; usbbuf[2]= 0xf0;
  usbbuf[3]= 0x03; usbbuf[4]= 0x70;
  usbbuf[5]= 0x07; usbbuf[6]= 0x60;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x05; usbbuf[2]= 0xe0;
  usbbuf[3]= 0x09; usbbuf[4]= 0xe0;
  usbbuf[5]= 0x09; usbbuf[6]= 0xc0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return 1;	/* shouldn't this be -1? */
    }

  return 12;
}

static int
draw_bug_c_1 (usb_dev_handle *udev)
{
  int r;

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x01; usbbuf[2]= 0xc0;
  usbbuf[3]= 0x05; usbbuf[4]= 0xe0;
  usbbuf[5]= 0x0f; usbbuf[6]= 0xe0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x0b; usbbuf[2]= 0x60;
  usbbuf[3]= 0x03; usbbuf[4]= 0x70;
  usbbuf[5]= 0x05; usbbuf[6]= 0xf0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x05; usbbuf[2]= 0xf0;
  usbbuf[3]= 0x03; usbbuf[4]= 0x70;
  usbbuf[5]= 0x0b; usbbuf[6]= 0x60;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return -1;
    }

  usbbuf[0]= AKIUSB_DRAW;
  usbbuf[1]= 0x0f; usbbuf[2]= 0xe0;
  usbbuf[3]= 0x05; usbbuf[4]= 0xe0;
  usbbuf[5]= 0x01; usbbuf[6]= 0xc0;
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 7, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE");
      return 1;
    }

  return 12;
}



#define BUG_STATE_MAX 6
#define BUG_STATE_DIV (BUG_STATE_MAX/2)

static int
draw_bug (usb_dev_handle *udev, int pattern_no)
{
  static int n = 0;
  int r;

  switch (pattern_no)
    {
    case 0:
      if (n/BUG_STATE_DIV)
	r = draw_bug_a_1 (udev);
      else
	r = draw_bug_a_0 (udev);
      break;
    case 1:
      if (n/BUG_STATE_DIV)
	r = draw_bug_b_1 (udev);
      else
	r = draw_bug_b_0 (udev);
      break;
    case 2:
    default:
      if (n/BUG_STATE_DIV)
	r = draw_bug_c_1 (udev);
      else
	r = draw_bug_c_0 (udev);
      break;
    }

  n++;
  if (n >= BUG_STATE_MAX)
    n = 0;

  return r;
}


/****

 Space Invador
 1979 Taito Corporation

    *     *
     *   *
    *******
   ** *** **
  ***********
  * ******* *
  * *     * *
     ** **


    *     *
  *  *   *  *
  * ******* *
  *** *** ***
  ***********
   *********
    *     *
   *       *


      **
     ****
    ******
   ** ** **
   ********
     *  *
    * ** *
   * *  * *


      **
     ****
    ******
   ** ** **
   ********
    * ** *
   *      *
    *    *


      ****
   **********
  ************
  ***  **  ***
  ************
     **  **
    ** ** **
  **        **


      ****
   **********
  ************
  ***  **  ***
  ************
    ***  ***
   **  **  **
    **    **

*****/
